package searchlogic

import (
	"gmap-backend/internal/server/vo"
	"gmap-backend/internal/util/strutil"
	"math"
	"strings"
)

const MAX_LETTERS_TITLE = 99999
const MAX_LETTERS_CONTENT = 200

// 把指定文本按关键词位置拆分为多个部分，并做字数限制处理
func calcFrags(str string, poses []MatchPos, hasKeyWords bool, maxLetters int) []vo.FragItem {
	// 不包含关键词，取字符串中满足长度的部分
	result := make([]vo.FragItem, 0, 4)
	if !hasKeyWords {
		tmp := str
		if len([]rune(str)) > maxLetters {
			tmp = strutil.SubstringByRuneIndexTo(str, maxLetters) + " ..."
		}
		result = append(result, vo.FragItem{
			Txt:     tmp,
			Keyword: false,
		})
		return result
	}

	// 包含关键词
	// 计算缩减的比例
	kwLetters := 0
	sumLetters := len([]rune(str))
	for _, pos := range poses {
		kwLetters += len([]rune(str[pos.from:pos.to]))
	}
	rate := float64(1)
	if sumLetters > kwLetters && maxLetters > kwLetters {
		rate = float64(maxLetters-kwLetters) / float64(sumLetters-kwLetters)
	}

	minPreserveLetters := 6
	shouldScale := true
	if rate >= 1 {
		shouldScale = false
	}

	// 计算缩放后的内容的函数
	scale := func(fragStr string) *vo.FragItem {
		if !shouldScale {
			return &vo.FragItem{
				Txt:     fragStr,
				Keyword: false,
			}
		}

		letters := len([]rune(fragStr))
		targetLetters := int(float64(letters) * rate)
		targetLetters = int(math.Min(math.Max(float64(targetLetters), float64(minPreserveLetters)), float64(letters)))

		if targetLetters == letters {
			return &vo.FragItem{
				Txt:     fragStr,
				Keyword: false,
			}
		}

		halfLetters := targetLetters / 2
		front := strutil.SubstringByRuneIndexTo(fragStr, halfLetters)
		end := strutil.SubstringByRuneIndexFrom(fragStr, letters-halfLetters)
		return &vo.FragItem{
			Txt:     front + " ... " + end,
			Keyword: false,
		}
	}

	// 根据每个部分字数的比例动态计算，并尽量保持不小于最小字数
	currPos := 0
	for _, pos := range poses {
		// 当前位置到关键词之前还有内容
		// 若不需要缩减，则直接把内容放入
		// 否则按对应比例的字数相应缩减，然后中间插入...
		if currPos < pos.from {
			fragStr := str[currPos:pos.from]
			result = append(result, *scale(fragStr))
		}
		// 关键词部分
		result = append(result, vo.FragItem{
			Txt:     str[pos.from:pos.to],
			Keyword: true,
		})
		currPos = pos.to
	}

	// 最后一个关键词后面还有内容
	if currPos < len(str) {
		fragStr := str[currPos:]
		result = append(result, *scale(fragStr))
	}
	return result
}

func DoSearch(list []MindMapInfo, searchers []ExpSearcher) []vo.SearchResult {
	result := make([]vo.SearchResult, 0, 4)
	for _, eachMap := range list {
		succ := true
		searchResults := make([]SearchResultItem, 0, 4)
		for _, searcher := range searchers {
			mapResult := searcher.DoMatch(&eachMap)
			if nil == mapResult {
				succ = false
				break
			}
			searchResults = append(searchResults, *mapResult)
		}
		if !succ {
			continue
		}
		combinedResult := combine(searchResults)
		result = append(result, vo.SearchResult{
			FullTitle:  strings.ReplaceAll(combinedResult.mapInfo.Title, " / ", "/"),
			Tags:       combinedResult.mapInfo.Tags,
			TitleParts: calcFrags(combinedResult.mapInfo.Title, combinedResult.titleKeywordPoses, combinedResult.titleHasKeyword, MAX_LETTERS_TITLE),
			ContParts:  calcFrags(combinedResult.mapInfo.Content, combinedResult.contentKeywordPoses, combinedResult.contentHasKeyword, MAX_LETTERS_CONTENT),
		})

	}
	return result
}
